package com.azier.influxdb.util;

import com.azier.influxdb.annotation.MyColumn;
import com.azier.util.DateUtil;
import org.influxdb.InfluxDBException;
import org.influxdb.InfluxDBMapperException;
import org.influxdb.annotation.Column;
import org.influxdb.annotation.Measurement;
import org.influxdb.dto.Point;
import org.influxdb.dto.QueryResult;

import java.lang.reflect.Field;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * InfluxDB数据库连接操作类
 *
 * @author 罗军
 * res_URL:https://blog.csdn.net/x541211190/article/details/83216589
 */
public class InfluxDBHandleUtil {

    private static DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT;
//        .ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
//        .withZone(ZoneId.of("UTC"));

    public static String getMeasurementName(Class<?> clazz) {
        return ((Measurement)clazz.getAnnotation(Measurement.class)).name();
    }

    public static String getDatabaseName(Class<?> clazz) {
        return ((Measurement)clazz.getAnnotation(Measurement.class)).database();
    }

    public static String getRetentionPolicy(Class<?> clazz) {
        return ((Measurement)clazz.getAnnotation(Measurement.class)).retentionPolicy();
    }

    public static TimeUnit getTimeUnit(Class<?> clazz) {
        return ((Measurement)clazz.getAnnotation(Measurement.class)).timeUnit();
    }
    public static void setField(
        final StringBuilder select,
        final Class<?> fieldType,
        final String columnName,
        final Object value) {
        if (boolean.class.isAssignableFrom(fieldType) || Boolean.class.isAssignableFrom(fieldType)) {
            select.append(columnName).append("=").append(value).append(" ");
        } else if (long.class.isAssignableFrom(fieldType) || Long.class.isAssignableFrom(fieldType)) {
            select.append(columnName).append("=").append(value).append(" ");
        } else if (double.class.isAssignableFrom(fieldType)
            || Double.class.isAssignableFrom(fieldType)) {
            select.append(columnName).append("=").append(value).append(" ");
        } else if (int.class.isAssignableFrom(fieldType) || Integer.class.isAssignableFrom(fieldType)) {
            select.append(columnName).append("=").append(value).append(" ");
        } else if (String.class.isAssignableFrom(fieldType)) {
            select.append(columnName).append("='").append(value).append("' ");
        } else {
            throw new InfluxDBMapperException(
                "Unsupported type " + fieldType + " for column " + columnName);
        }
    }
    public static void setTime(
        final Point.Builder pointBuilder,
        final Class<?> fieldType,
        final TimeUnit timeUnit,
        final Object value) {
        if (Instant.class.isAssignableFrom(fieldType)) {
            Instant instant = (Instant) value;
            long time = timeUnit.convert(instant.toEpochMilli(), TimeUnit.MILLISECONDS);
            pointBuilder.time(time, timeUnit);
        } else {
            throw new InfluxDBMapperException(
                "Unsupported type " + fieldType + " for time: should be of Instant type");
        }
    }
    public static void setField(
        final Point.Builder pointBuilder,
        final Class<?> fieldType,
        final String columnName,
        final Object value) {
        if (boolean.class.isAssignableFrom(fieldType) || Boolean.class.isAssignableFrom(fieldType)) {
            pointBuilder.addField(columnName, (boolean) value);
        } else if (long.class.isAssignableFrom(fieldType) || Long.class.isAssignableFrom(fieldType)) {
            pointBuilder.addField(columnName, (long) value);
        } else if (double.class.isAssignableFrom(fieldType)
            || Double.class.isAssignableFrom(fieldType)) {
            pointBuilder.addField(columnName, (double) value);
        } else if (int.class.isAssignableFrom(fieldType) || Integer.class.isAssignableFrom(fieldType)) {
            pointBuilder.addField(columnName, (int) value);
        } else if (String.class.isAssignableFrom(fieldType)) {
            pointBuilder.addField(columnName, (String) value);
        } else {
            throw new InfluxDBMapperException(
                "Unsupported type " + fieldType + " for column " + columnName);
        }
    }
    public static void setTime(
        final StringBuilder select,
        final Class<?> fieldType,
        final Object value) {
        if (Instant.class.isAssignableFrom(fieldType)) {
            Instant instant = (Instant) value;
            DateTimeFormatter formatter = DateTimeFormatter
                .ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
                .withZone(ZoneId.of("UTC"));
            String timeP1 = formatter.format(instant);
            select.append("time='").append(timeP1).append("' ");
        } else {
            throw new InfluxDBMapperException(
                "Unsupported type " + fieldType + " for time: should be of Instant type");
        }
    }
    public static List<List<Map<String,Object>>> analysisResult(QueryResult queryResult) throws InfluxDBException {
        throwExceptionIfResultWithError(queryResult);
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Map<String,Object>>> outerList = new ArrayList<>();
        for(QueryResult.Result result:resultList){
            if(result.getSeries()!=null&&!result.getSeries().isEmpty()){
                List<Map<String,Object>> innerList = new ArrayList<>();
                outerList.add(innerList);
                for(QueryResult.Series series:result.getSeries()){
                    String name = series.getName();
                    List<String> colums = series.getColumns();
                    Map<String,String> tagsMap = series.getTags();
                    List<List<Object>> valueList = series.getValues();
                    if (valueList != null && valueList.size() > 0) {
                        for (List<Object> value : valueList) {
                            int i = 0;
                            Map<String, Object> map = new HashMap<String, Object>();
                            for (String colum : colums) {
                                map.put(colum,value.get(i));
                                i++;
                            }
                            innerList.add(map);
                        }
                    }
                }
            }
        }
        return outerList;
    }

    /**
     * 只解析第一个sql的结果集
     * @param queryResult
     * @return
     * @throws InfluxDBException
     */
    public static List<Map<String,Object>> analysisResultOne(QueryResult queryResult) throws InfluxDBException {
        List<List<Map<String,Object>>> lists = analysisResult(queryResult);
        if(lists!=null&&!lists.isEmpty()){
            return lists.get(0);
        }
        return null;
    }
    /**
     * 反射获取类的Column的注解信息
     *
     */
    public static ConcurrentHashMap<String,Field> getColumn(Class clazz){
        ConcurrentHashMap<String,Field> influxColumnAndFieldMap = new ConcurrentHashMap<>();
        Class<?> c = clazz;
        while (c != null) {
            for (Field field : c.getDeclaredFields()) {
                Column colAnnotation = field.getAnnotation(Column.class);
                if (colAnnotation != null) {
                    influxColumnAndFieldMap.put(colAnnotation.name(), field);
                }
            }
            c = c.getSuperclass();
        }
        return influxColumnAndFieldMap;
    }

    public static ConcurrentHashMap<String,Field> getTagColumn(Class clazz){
        ConcurrentHashMap<String,Field> influxColumnAndFieldMap = new ConcurrentHashMap<>();
        Class<?> c = clazz;
        while (c != null) {
            for (Field field : c.getDeclaredFields()) {
                Column colAnnotation = field.getAnnotation(Column.class);
                if (colAnnotation != null&&colAnnotation.tag()) {
                    influxColumnAndFieldMap.put(colAnnotation.name(), field);
                }
            }
            c = c.getSuperclass();
        }
        return influxColumnAndFieldMap;
    }
    public static ConcurrentHashMap<String,List<Field>> getAllColumnList(Class clazz){
        ConcurrentHashMap<String,List<Field>> influxColumnAndFieldMap = new ConcurrentHashMap<>();
        Class<?> c = clazz;

        while (c != null) {
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                List<Field> fieldList = influxColumnAndFieldMap.get(field.getName());
                if(fieldList==null){
                    fieldList = new ArrayList<>();
                    influxColumnAndFieldMap.put(field.getName(), fieldList);
                }
                fieldList.add(field);
            }
            c = c.getSuperclass();
        }
        return influxColumnAndFieldMap;
    }
    public static ConcurrentHashMap<String,Field> getNoTagColumn(Class clazz){
        ConcurrentHashMap<String,Field> influxColumnAndFieldMap = new ConcurrentHashMap<>();
        Class<?> c = clazz;
        while (c != null) {
            for (Field field : c.getDeclaredFields()) {
                Column colAnnotation = field.getAnnotation(Column.class);
                if (colAnnotation != null&&!colAnnotation.tag()) {
                    influxColumnAndFieldMap.put(colAnnotation.name(), field);
                }
            }
            c = c.getSuperclass();
        }
        return influxColumnAndFieldMap;
    }
    /**
     * 反射获取类的Column的列表信息，不论是否含有注解
     *
     */
    public static ConcurrentHashMap<String,Field> getMyColumnAnnotation(Class clazz){
        ConcurrentHashMap<String,Field> influxColumnAndFieldMap = new ConcurrentHashMap<>();
        Class<?> c = clazz;
        while (c != null) {
            for (Field field : c.getDeclaredFields()) {
                MyColumn colAnnotation = field.getAnnotation(MyColumn.class);
                if (colAnnotation != null) {
                    influxColumnAndFieldMap.put(colAnnotation.name(), field);
                }
            }
            c = c.getSuperclass();
        }
        return influxColumnAndFieldMap;
    }
    public static void throwExceptionIfResultWithError(QueryResult queryResult) {
        if (queryResult.getError() != null) {
            throw new InfluxDBMapperException("InfluxDB returned an error: " + queryResult.getError());
        } else {
            queryResult.getResults().forEach((seriesResult) -> {
                if (seriesResult.getError() != null) {
                    throw new InfluxDBMapperException("InfluxDB returned an error with Series: " + seriesResult.getError());
                }
            });
        }
    }

    public static String dataFormat(Date date){
        return instantFormat(Instant.ofEpochMilli(date.getTime()));
    }

    public static String instantFormat(Instant instant){
        return FORMATTER.format(instant);
    }

    public static Instant instantParse(String instantStr){
        return Instant.parse(instantStr);
    }

    public static Instant dataParse(String dateStr){
        return DateUtil.parseDateTime(dateStr).toInstant();
    }
}


